from market.models import UserProfile, BetRoom
from market.models import Player, Strategy, Asset, AssetPercentageInStrategy, Fund
from market.view import View
from market.marketData import MarketData
from market.mathView import vec2view
from market.math import isARepartition
from market.historyFactory import createHistory
from market.strategyAndFundHelper import getPortfolioMtM, isStrategyValid, getFundMtM, getPercOnAssetInStrat, getNumberOfSpecifiedAssetInFund
from market.stupidUserException import StupidUserException


# helper func
def _add(x,y):
        return x+y


# this class contains all the info on a given player
# this object is the one given to the view that will display it
class PlayerData:

        ###########################################################################################
	#
	#          PRIVATE
	#
        ###########################################################################################

        ###########################################################################################
	# the creation of this object should give all the needed information
	def __init__(self, user, betroom):
		# we get the player from the DB
                try:
                        self.dbPlayer = Player.objects.get( userBeingThisPlayer = user, mainStrategy__betroom = betroom)
                except Player.DoesNotExist:
                        raise Exception("you have to create the player " + playerID + " before making a PlayerData of it" )

        ###########################################################################################
	#
	#          PUBLIC
	#
        ###########################################################################################

	###########################################################################################
	# returns the name
	def getName(self):
		return self.dbPlayer.userBeingThisPlayer.username


        ###########################################################################################
        # return the number of specified asset in the fund
	def getNumberOfSpecifiedAssetInMainFund(self, asset):
		return self.getMainFundTotalNumberOfAssets() * getPercOnAssetInStrat( self.dbPlayer.mainStrategy, asset)
		
		
        ###########################################################################################
        # returns the number of specified asset accros all the funds the player is holding
	def getNumberOfSpecifiedAssetInAllFunds(self, asset):
                return reduce(_add,[ getNumberOfSpecifiedAssetInFund( f, asset ) for f in Fund.objects.filter( player = self.dbPlayer ) ],0)
        

        ###########################################################################################
	# return the number of assets in the fund
	def getMainFundTotalNumberOfAssets(self):
		return Fund.objects.get(
                        player = self.dbPlayer,
                        strategy = self.dbPlayer.mainStrategy
                        ).numberOfAssetsInFund
        

        ###########################################################################################
        # return the mtm of the portfolio (composed of different strategies and number of assets associated to them)
        def getMainFundMtM(self, marketData):
                return getFundMtM(
                        self.dbPlayer,
                        marketData,
                        self.dbPlayer.mainStrategy
                        )

        ###########################################################################################
        # return the mtm of the portfolio (composed of different strategies and number of assets associated to them)
        def getPortfolioMtM(self, marketData):
                return getPortfolioMtM( self.dbPlayer, marketData )
        
        ###########################################################################################
	# set the number of asset per strategy using the MtM, the MtM repartition and the market prices
	# here we allow to invest a strategy whom mtm is 0. This maybe unsafe. The reason is because we should be checking this before
        def setNumberOfAssetPerStrategy( self, mtmOfPortfolio, percentagesInFunds, market ):
                #We first check that we only invest fund for wich the target is not 0 
                myTargetFunds = [f for f in Fund.objects.filter( player = self.dbPlayer )]
                #check that all the fund to invest are ok!
                for f in percentagesInFunds:
                        if f[1] != 0:
                                index = myTargetFunds.index(f[0])
                                if myTargetFunds[index].fundTargetPercentageInPortfolio == 0:
                                        raise Exception('Trying to invest a fund which is not in the investement target')
                                mtm_fund = f[1] * mtmOfPortfolio
                                mtm_strat = market.getStrategyMtM( myTargetFunds[index].strategy )
                                if mtm_strat != 0 :
                                        myTargetFunds[index].numberOfAssetsInFund = mtm_fund / mtm_strat
                                else:
                                        myTargetFunds[index].numberOfAssetsInFund = 0
                                myTargetFunds[index].save()
                        
                
        ###########################################################################################
        # this the implementation of rule 2 for the changement of strategy from a player. All rules 2 XXX need to call this function ultimatly 
        def setNewView( self, repartitionInput ):

                assets = [ a[0] for a in repartitionInput ]
                repartition = [ a[1] for a in repartitionInput ]
                newView = View( assets, repartition )
                tmp = isARepartition(repartition)
                #check the repartition
                if(  tmp[0] == False ):
                        errorStr = " " + str(self.getName()) + " wants to submit: ["
                        for i in range(len(repartition)):
                               errorStr +=  "  %s " %repartition[ i ]
                        errorStr += "] and it is  not a repatition: "+ tmp[1]+ ". Nothing done!"
                        raise Exception( errorStr )

                # register the values with the previous market
                p = self.dbPlayer
                m1 = MarketData( p.mainStrategy.betroom )
                constantMtM = self.getPortfolioMtM( m1 )
                if constantMtM != 0 :
                        percentagesInFunds = [ (f,getFundMtM(p, m1, f.strategy)/ constantMtM) for f in Fund.objects.filter( player = p ) ]
                else:
                        percentagesInFunds = [ 0 for f in Fund.objects.filter( player = p ) ]
                mainStratValueBefore = m1.getStrategyMtM( p.mainStrategy )

                # we only want standard assets in main strategies
                for a in assets:
                        if not( a.isAvailableForStandardStrategy ):
                                raise Exception( "Something went wrong. The repartition should have the same size than the number of standard asset. Contact admin. No action Performed")
               
                # check that the new strategy will not have a null price: We are looking for an asset with a non zero percentage which is
                # not artificialy set to 0.
                hasNullPrice = True
                for a in repartitionInput:
                        if a[1]!=0 and hasNullPrice:
                                if a[0].fixValue > 0 or (a[0].fixValue == -1 and a[0].valueMultiplier > 0):
                                      hasNullPrice = False
                if hasNullPrice:
                        raise Exception( "Please contact admin. The Transation has not been performed")
        
                
                # first we update proba which already exist
                for prob in p.mainStrategy.assetpercentageinstrategy_set.all():
                        prob.assetPercentageInStrategy = newView.getViewOnAsset( prob.asset )
                        prob.save()
                # then we add the proba on asset which are not yet in the strategy.
                # this allow fo instance to add an asset later on in the data base and change nothing else
                for a in range(len(assets)):
                        if( repartition[ a ] != 0 ):
                                temp = AssetPercentageInStrategy.objects.filter( strategy = p.mainStrategy, asset = assets[ a ] )
                                if( len( temp ) == 0 ):
                                        # we found a new asset which was not in the previous strategy and therefore need to be added
                                        newProba = AssetPercentageInStrategy(
                                                asset = assets[ a ],
                                                strategy = p.mainStrategy,
                                                assetPercentageInStrategy = repartition[ a ] )
                                        newProba.save()
                                        
                # we now have access to the new market in the standard way because the new main strategy has been set
                m2 = MarketData(p.mainStrategy.betroom)
                
                # We now modify the number of asset per strategy.
                # We have to distinguish between the player who is changing his main strategy and the other.
                # for the player changing his strategy, the behaviour is equivalent to :
                #       1) put 100% of the MtM on the main strategy.
                #       2) change the strategy (the all portfolio being at stake)
                #       3) set the "set percentage in strategies" back
                # for the other player, only the number of asset of the fund linked to the strategy being change has to be updated.
                
                # do the job for the player
                self.setNumberOfAssetPerStrategy(constantMtM,percentagesInFunds,m2)
                # do the job for the other players: update the number of asset in strategy for all the fund associated to the player's main strategy.
                listOfFundUsingThisStrat = Fund.objects.filter( strategy = p.mainStrategy )
                if len(listOfFundUsingThisStrat) > 1 :# the player case has already been treated
                        # we do not test if the MtM of the strategy is zero or not. It is not supposed to be possible since we tested that at least
                        # one of the asset with a non-zero percentage had no 0 overwritten value. This asset is then sure to have a non zero price
                        # since the non zero percetage * valueMultiplier / Nplayer is a lower bound for the price
                        MtMRatio = mainStratValueBefore / m2.getStrategyMtM( p.mainStrategy )
                        for f in listOfFundUsingThisStrat:
                                if f.player != p:
                                        f.numberOfAssetsInFund = MtMRatio * f.numberOfAssetsInFund
                                        f.save()
                
                # create history
                createHistory(m2,[p.mainStrategy], p, "sets a new view" )
                return

        

        ###########################################################################################
        # set the new strategy mtm repartition.
        # when set the main strat percentage must be greater than 50%
        # this does not hold as the market move, but each time the player change its main strat,
        # the MtM repartition we are definng with this function is set
        def setNewStratMtMRep( self, repartition ):
                strats = [ r[0] for r in repartition ]
                rep  = [ r[1] for r in repartition ] 
                # check that the main
                tmp = isARepartition(rep)
                #check the repartition
                if(  tmp[0] == False ):
                        errorStr = " As you surely know : ["
                        for i in range(len(repartition)- 1) :
                               errorStr += "  %s,  " % str(repartition[ i ][1])
                        errorStr += str( repartition[len(repartition)- 1][1] ) + " ] is  not a repatition: "+ tmp[1]+ ". Nothing done!"
                        raise StupidUserException( errorStr )
                # check that we have at least 50% on the main strat
                mainStratFund = False
                for i in range(len(strats)):
                        tmp= isStrategyValid(strats[i])
                        if not( tmp[0] ):
                                raise Exception( tmp[1])
                        if strats[ i ] == self.dbPlayer.mainStrategy:
                                mainStratFund = True
                                if rep[ i ] < 0.5:
                                        raise StupidUserException( "Main strat must have 50 percent of the MtM at least" )
                if not(mainStratFund):
                        raise Exception( "main strategie fund not found" )
                        

                # do the job:
                # record the mtm of the portfolio to be able to set the correct number of assets per strategy
                mkt = MarketData(self.dbPlayer.mainStrategy.betroom)
                constantMtM = self.getPortfolioMtM( mkt )
                        
                for i in range(len(strats)):
                        if( rep[i] != 0.):# we update or create the investement with the correct repartition
				try:
                                        fund = Fund.objects.get( player = self.dbPlayer, strategy = strats[i] )
					fund.fundTargetPercentageInPortfolio = rep[i]
					fund.save()
				except Fund.DoesNotExist:
                                        fund = Fund(
						player = self.dbPlayer,
                                                strategy = stratToAdd[0],
                                                fundTargetPercentageInPortfolio = float(rep[i]),
                                                numberOfAssetsInFund = 0 # dummy, will be set later calling setNumberOfAssetPerStrategy
                                                )
                                        fund.save()
                        
                        else:# the repartition is 0 and we delete the investement
                                alreadyExist =  False
                                try:
                                        fund = Fund.objects.get( player = self.dbPlayer, strategy = strats[i] )
                                        alreadyExist =  True
                                except Fund.DoesNotExist:
                                        pass
                                if alreadyExist:# we actually delete
                                        fund.delete()

                # set the number of asset accordingly to the percentage and the MTM accordingly
                percentagesInFunds = [ (f,f.fundTargetPercentageInPortfolio) for f in Fund.objects.filter( player = self.dbPlayer ) ]
                self.setNumberOfAssetPerStrategy( constantMtM, percentagesInFunds, mkt )
                createHistory( mkt,[strat for strat in Strategy.objects.filter(betroom=self.dbPlayer.mainStrategy.betroom)], self.dbPlayer, "changes his investements in the funds" )
                return
                
                
                


